"""
生成一维山脉
"""

import math
import random
import matplotlib.pyplot as plt
from matplotlib import gridspec
from matplotlib.colors import BoundaryNorm
from matplotlib.ticker import MaxNLocator
from matplotlib import ticker, cm
import numpy as np


class Vector:
    x: float
    y: float

    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __len__(self):
        """取模运算"""
        return (self.x ** 2 + self.y ** 2) ** 0.5

    def __mul__(self, other):
        """点乘机运算"""
        return self.x * other.x + self.y * other.y

    @classmethod
    def getByTwoPoint(cls, x1, y1, x2, y2):
        x = x1 - x2
        y = y1 - y2
        return cls(x, y)


class Noise:
    def __init__(self, diff: int, loud: float, seed: float):
        self.diff = diff  # 间距
        self.loud = loud  # 上下幅度
        self.seed = seed  # 种子
        ...

    def getNose(self, x: int):
        """获取地形噪音"""
        if x % self.diff != 0:
            # raise Exception("获取噪音位置错误")
            return 0
        random.seed(x + self.seed)
        return random.uniform(-1, 1) * self.loud

    def getLeftRightX(self, x: int):
        """获取该噪音网络两端噪音源位置"""
        if x % self.diff == 0:
            return x, x + self.diff
        x1 = x - x % self.diff
        return x1, x1 + self.diff

    def getBuff(self, x: int):
        """获得该噪音中某位置下的加权、对地形的增益效果"""
        x1, x2 = self.getLeftRightX(x)
        n1 = self.getNose(x1)
        n2 = self.getNose(x2)

        def s(xi):
            return 3 * xi ** 2 - 2 * xi ** 3

        q2 = s((x - x1) / (x2 - x1))
        q1 = 1 - q2
        return n1 * q1 + n2 * q2


class Noise2D:
    def __init__(self, diff: int, seed: float, loud: float):
        self.diff = diff
        self.seed = seed
        self.loud = loud  # 上下幅度
        ...

    # def getCoreNoiseVector(self, x: int, y: int) -> Vector:
    #     """获取角落噪声向量"""
    #     random.seed(str(self.seed) + f"{x}, {y}" + "x")
    #     vx = random.uniform(0, 1)
    #     random.seed(str(self.seed) + f"{x}, {y}" + "y")
    #     vy = random.uniform(0, 1)
    #     return Vector(vx, vy)

    def getNoisePosition(self, x: int, y: int) -> list:
        """返回四个噪音位置"""
        if x % self.diff == 0:
            xMin = x
            xMax = x + self.diff
        else:
            xMin = x - x % self.diff
            xMax = xMin + self.diff
        if y % self.diff == 0:
            yMin = y
            yMax = y + self.diff
        else:
            yMin = y - y % self.diff
            yMax = yMin + self.diff
        # 左上 右上 左下 右下
        return [(xMin, yMax), (xMax, yMax), (xMin, yMin), (xMax, yMin)]

    def getCoreNoise(self, x: int, y: int) -> float:
        p1, p2, p3, p4 = self.getNoisePosition(x, y)
        for p in [p1, p2, p3, p4]:
            if p == (x, y):
                random.seed(f"{x}-{y}-{self.seed}")
                # return self.getCoreNoiseVector(*p) * Vector.getByTwoPoint(*p, x, y)
                return random.uniform(-1, 1) * self.loud
        return 0

    def getBuff(self, x: int, y: int):
        def s(xi):
            return 3 * xi ** 2 - 2 * xi ** 3

        p1, p2, p3, p4 = self.getNoisePosition(x, y)
        xMin, yMax = p1
        xMax, yMin = p4
        qRight = s((x - xMin) / self.diff)
        qLeft = 1 - qRight
        qTop = s((y - yMin) / self.diff)
        qDown = 1 - qTop

        n1 = self.getCoreNoise(*p1)
        n2 = self.getCoreNoise(*p2)
        n3 = self.getCoreNoise(*p3)
        n4 = self.getCoreNoise(*p4)

        return n1 * (qLeft * qTop) + n2 * (qRight * qTop) + n3 * (qLeft * qDown) + n4 * (qRight * qDown)

    def print(self, xMin: int, yMin: int, xMax: int, yMax: int):
        for y in range(yMin, yMax):
            for x in range(xMin, xMax):
                print(self.getBuff(x, y), end="\t")
            print()

    def showPlot(self, xMin: int, yMin: int, xMax: int, yMax: int):
        Z = []
        xArr = list(range(xMin, xMax + 1))
        yArr = list(range(yMin, yMax + 1))
        for y in yArr:
            line = [self.getBuff(x, y) for x in xArr]
            Z.append(line)
        fig, ax = plt.subplots()

        ax.pcolormesh(xArr, yArr, Z)
        plt.show()


class Ground2D:
    def __init__(self, seed: float, vHeight):
        self.seed = seed
        self.vHeight = vHeight
        self.noiseArr = [
            Noise2D(100, seed + 50, 50),
            Noise2D(30, seed + 20, 20),
            Noise2D(10, seed + 10, 5)
        ]
        ...

    def getHeight(self, x: int, y: int):
        sumBuff = sum(noise.getBuff(x, y) for noise in self.noiseArr)
        return self.vHeight + sumBuff

    def getCarve(self, x: int, y: int):
        sumBuff = sum(noise.getBuff(x, y) for noise in self.noiseArr)
        return sumBuff > 5 if 1 else 0

    def showPlot(self, xMin: int, yMin: int, xMax: int, yMax: int):
        Z = []
        xArr = list(range(xMin, xMax + 1))
        yArr = list(range(yMin, yMax + 1))
        for y in yArr:
            line = [self.getHeight(x, y) for x in xArr]
            Z.append(line)
        fig, ax = plt.subplots()
        # ax.pcolormesh(xArr, yArr, Z)
        plt.contourf(xArr, yArr, Z, cmap="terrain")
        ax.contour(xArr, yArr, Z, )
        plt.show()


class Ground:
    def __init__(self, seed: float, vHeight: float):
        self.seed = seed
        self.vHeight = vHeight
        self.noiseArr = [
            Noise(20, 20, seed),
            Noise(18, 20, seed + 5),
            Noise(20, 10, seed + 10),
            Noise(5, 5, seed + 20),
            # Noise(2, 1, seed)
        ]
        ...

    def getHeight(self, x: int):
        sumBuff = sum(noise.getBuff(x) for noise in self.noiseArr)
        return self.vHeight + sumBuff

    def showPlot(self, leftX: int, rightX: int):
        xArr = list(range(leftX, rightX + 1))
        arr = [self.getHeight(_) for _ in range(leftX, rightX + 1)]

        # ax1 = plt.subplot(len(self.noiseArr) + 1, 1, 1)
        # gs = gridspec.GridSpec(1, 2, width_ratios=[3, 1])
        ax1 = plt.subplot(len(self.noiseArr), 2, 1)
        ax1.set_title("main ground")
        plt.plot(xArr, arr)
        plt.ylim(0, 100)

        for i in range(len(self.noiseArr)):
            # ax = plt.subplot(len(self.noiseArr) + 1, 1, i + 2)
            ax = plt.subplot(len(self.noiseArr), 2, (i + 1) * 2)
            ax.set_title(f"noise{i}")
            yArr = [self.noiseArr[i].getNose(x) for x in range(leftX, rightX + 1)]
            plt.bar(xArr, yArr)
            plt.axhline(y=0, c="red")
            plt.ylim(-20, 20)
                # plt.grid()

        # plt.ylim((0, 100))
        plt.show()
        ...


def main():
    seed = 1232
    # gd = Ground2D(seed, 50)
    # gd.showPlot(100, 100, 500, 500)
    # gd = Ground2D(seed, 50)
    # gd.showPlot(100, 100, 500, 500)

    g = Ground(seed, 50)
    g.showPlot(100, 200)
    ...


if __name__ == '__main__':
    main()
